Skip to content

Add support @Target annotation#2548

Open
thomaswoeckinger wants to merge 1 commit into
smallrye:mainfrom
thomaswoeckinger:target_annotation
Open

Add support @Target annotation#2548
thomaswoeckinger wants to merge 1 commit into
smallrye:mainfrom
thomaswoeckinger:target_annotation

Conversation

@thomaswoeckinger

Copy link
Copy Markdown

As discussed on microprofile/microprofile-graphql#349 this add support for @target annotation similar to @source annotation.

@phillip-kruger would be great if you have time to review

@mskacelik

Copy link
Copy Markdown
Contributor

My knowledge of the Apollo Federation is quite limited - but how is the @Target API connected to the federation, given that it is similar to @Source, which is not related to the federation?

@thomaswoeckinger

Copy link
Copy Markdown
Author

My knowledge of the Apollo Federation is quite limited - but how is the @Target API connected to the federation, given that it is similar to @Source, which is not related to the federation?

This was simply the wrong package name, as mentioned in the link of the description this annotation does not exist yet in the eclipse-microprofile, so a new namespace is needed.

Any suggestions?

@mskacelik

Copy link
Copy Markdown
Contributor

My knowledge of the Apollo Federation is quite limited - but how is the @Target API connected to the federation, given that it is similar to @Source, which is not related to the federation?

This was simply the wrong package name, as mentioned in the link of the description this annotation does not exist yet in the eclipse-microprofile, so a new namespace is needed.

Any suggestions?

I would have just moved the annotation to the api package, where rest of the experimental annotations are.

Regarding the PR, I barely skimmed trought your test case atleast, but they look bit different to @t1 specification, and having it mixed with federation API does not help it either (amongst other non-trivial GraphQL keywords).

Regarding test placement, I would have taken look at both:

@phillip-kruger

Copy link
Copy Markdown
Member

Maybe @t1 can have a look ?

@phillip-kruger phillip-kruger requested review from jmartisk and t1 July 2, 2026 00:23
@jmartisk

jmartisk commented Jul 2, 2026

Copy link
Copy Markdown
Member

Please also add some short text and examples into the documentation

@t1

t1 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

I wrote an IT to show how I had imagined for this to work; and TODOs on how it does not.

I don't quite understand how your solution is intended to work; is it about Federation? That would also be cool, but not what I had on my mind when I wrote #349.

Note that there was a flaw in the original suggestion: we can't update the team as an object; we need to update the team id.

Here's my test that shows @Source as well as @Target.

package io.smallrye.graphql.tests.resolvers;

import static org.assertj.core.api.BDDAssertions.then;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Mutation;
import org.eclipse.microprofile.graphql.Name;
import org.eclipse.microprofile.graphql.Query;
import org.eclipse.microprofile.graphql.Source;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import io.smallrye.graphql.api.federation.Target;
import io.smallrye.graphql.tests.GraphQLAssured;

@ExtendWith(ArquillianExtension.class)
@RunAsClient
public class ResolverTest {
    @Deployment
    public static WebArchive deployment() {
        return ShrinkWrap.create(WebArchive.class, "mutation-target-test.war")
                .addAsResource(new StringAsset("mp.graphql.showErrorMessage=*"), "META-INF/microprofile-config.properties")
                .addClasses(SuperHero.class, Team.class, SuperHeroResource.class);
    }

    public static class SuperHero {
        private String id;
        private String name;

        @SuppressWarnings("unused")
        public SuperHero() {
        }

        public SuperHero(String id, String name) {
            this.id = id;
            this.name = name;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    public static class Team {
        private String id;
        private String name;

        @SuppressWarnings("unused")
        public Team() {
        }

        public Team(String id, String name) {
            this.id = id;
            this.name = name;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    @GraphQLApi
    public static class SuperHeroResource {
        private static final Map<String, SuperHero> SUPER_HEROES = Map.of(
                "iron-man", new SuperHero("iron-man", "Iron Man"),
                "spider-man", new SuperHero("spider-man", "Spider Man")
        );
        private static final Map<String, Team> TEAMS = Map.of(
                "avengers", new Team("avengers", "Avengers")
        );
        private static final Map<String, String> SUPER_HERO_TEAMS = new HashMap<>(Map.of(
                "iron-man", "avengers"
        ));

        @Query
        public SuperHero get(String id) {
            var superHero = SUPER_HEROES.get(id);
            if (superHero == null) {
                throw new IllegalArgumentException("unknown super hero; only have " + SUPER_HEROES.keySet());
            }
            return superHero;
        }

        public Team team(@Source SuperHero superHero) {
            var teamId = SUPER_HERO_TEAMS.get(superHero.id);
            if (teamId == null) {
                return null;
            }
            var team = TEAMS.get(teamId);
            if (team == null) {
                throw new IllegalArgumentException("unknown team; only have " + TEAMS.keySet());
            }
            return team;
        }

        @Mutation
        public SuperHero update(@Name("superHero") SuperHero patch) {
            var superHero = get(patch.getId());
            if (patch.getName() != null) {
                superHero.setName(patch.getName());
            }
            return superHero;
        }

        // TODO this should be unnecessary (see blow)
        @Mutation
        public void updateTeam(String superHeroId, String teamId) {
            SUPER_HERO_TEAMS.put(superHeroId, teamId);
        }

        @SuppressWarnings("unused")
        public void updateTeam(@Target SuperHero superHero, String teamId) {
            SUPER_HERO_TEAMS.put(superHero.getId(), teamId);
        }
    }

    @ArquillianResource
    URL testingURL;
    GraphQLAssured gql;

    @BeforeEach
    void setUp() {
        gql = new GraphQLAssured(testingURL);
    }

    @Test
    public void shouldGetSuperHeroWithTeam() {
        var response = gql.post("""
                query {
                  get(id: "iron-man") {
                    name
                    team { name }
                  }
                }""");

        then(response).isEqualTo("""
                {"data":{"get":{"name":"Iron Man","team":{"name":"Avengers"}}}}""");
    }

    @Test
    public void shouldUpdateTeam() {
        // TODO this should be unnecessary if we could provide the rating directly with the update mutation below (commented out)
        then(gql.post("""
                mutation{updateTeam(superHeroId:"spider-man" teamId:"avengers")}""")
        ).isEqualTo("""
                {"data":{"updateTeam":null}}""");

        var response = gql
                .post("""
                        mutation {
                          update (superHero: {
                            id: "spider-man"
                            name: "Ultimate Spiderman"
                            # teamId: "avengers" # <- this is the @Target field
                          }) {
                            name
                            team {name}
                          }
                        }""");

        then(response).isEqualTo("""
                {"data":{"update":{"name":"Ultimate Spiderman","team":{"name":"Avengers"}}}}""");
        then(gql.post("""
                query { get(id:"spider-man") { team { name }}}""")
        ).isEqualTo("""
                {"data":{"get":{"team":{"name":"Avengers"}}}}""");
    }
}

@thomaswoeckinger

thomaswoeckinger commented Jul 3, 2026

Copy link
Copy Markdown
Author

Got your point, as needed this too, this is the completely changed PR.

A point which needs to be discussed is the design behind InputType, currently i add a separate field targetFields, which may not be necessary, as Operation already extends Field, but currenlty the code does not add any Operation to 'fields'

Add also some documentation to the @target annotation, if more is required plz give me a hint where to add it.

@thomaswoeckinger thomaswoeckinger force-pushed the target_annotation branch 2 times, most recently from 907236d to 6a8dd0d Compare July 3, 2026 08:57
@mskacelik

mskacelik commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Add also some documentation to the @Target annotation, if more is required plz give me a hint where to add it.

@thomaswoeckinger

Copy link
Copy Markdown
Author

Add also some documentation to the @Target annotation, if more is required plz give me a hint where to add it.

* https://github.com/smallrye/smallrye-graphql/tree/main/docs

* https://github.com/smallrye/smallrye-graphql/blob/main/mkdocs.yml

Added documentation and small example to the existing typesafe-client-usage.md.
Any suggestions are welcome.

Comment thread docs/typesafe-client-usage.md Outdated
Introduce @target annotation, so GraphQL input types can be extended
like GraphQL types using @source annotation
@thomaswoeckinger

thomaswoeckinger commented Jul 6, 2026

Copy link
Copy Markdown
Author

Any ideas suggestions about the design behind InputType, currently i add a separate field targetFields, which may not be necessary, as Operation already extends Field, but currently the code does not add any Operation to 'fields'.

so @t1 and @jmartisk any suggestions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants